// Copyright 2006 Two Pair, LLC. All Rights Reserved.
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;

/**
 * Counts the number of occurrences of all phrases in the sequence of words
 * being processed.
 */
public class PhraseCounter implements WordParser {

  /**
   * Constructs a phrase counter with the specified word analyzer and phrase
   * length.
   * 
   * @param wAnalyzer determines which words are stop words and non-stop-words
   * @param phraseLength the number of words in a phrase
   */
  public PhraseCounter(WordAnalyzer wAnalyzer, int phraseLength) {
    this.wAnalyzer = wAnalyzer;
    this.phraseLength = phraseLength;
  }

  /**
   * Print the results to the specified writer.
   * 
   * @param out the writer stream to write to
   * @param threshold frequency with which a phrase must occur to be printed
   */
  public void printResults(Writer out, int threshold) throws IOException {
    // Use a tree set to sort all of the accumulated phrases
    TreeSet treeSet = new TreeSet();
    for (Iterator it = phraseMap.keySet().iterator(); it.hasNext(); ) {
      Phrase phrase = (Phrase) it.next();
      if (phrase.getFrequency() >= threshold) {
        treeSet.add(phrase);
      }
    }

    // different printing rules apply to words vs. phrases
    if (phraseLength == 1) {
      // single-word phrases are of the form
      // 3: "dog"\n
      for (Iterator it = treeSet.iterator(); it.hasNext();) {
        Phrase phrase = (Phrase) it.next();
        out.write(String.valueOf(phrase.getFrequency()));
        out.write(": \"");
        out.write(phrase.getWords()[0]);
        out.write("\"\n");
      }
    } else {
      // multi-word phrases are of the form
      // 3: <"dog", "cat", "squirrel">\n
      for (Iterator it = treeSet.iterator(); it.hasNext();) {
        Phrase phrase = (Phrase) it.next();
        String[] words = phrase.getWords();
        out.write(String.valueOf(phrase.getFrequency()));
        out.write(": <\"");
        out.write(words[0]);
        for (int i = 1; i < phraseLength; ++i) {
          out.write("\", \"");
          out.write(words[i]);
        }
        out.write("\">\n");
      }
    }

    // flush all buffers
    out.flush();
  }

  /*
   * @see WordParser#processWord(java.lang.String)
   */
  public void processWord(String word) {
    // canonicalize as lower case
    word = Util.toLowerCase(word);

    // ignore stop words
    if (wAnalyzer.isStopWord(word)) {
      return;
    }

    // add to the word buffer
    wordBuffer.addLast(word);

    // do we have complete phrase?
    int bufSize = wordBuffer.size();
    if (bufSize == phraseLength) {
      // yes we do, create a new phrase
      String[] wordArray = (String[]) wordBuffer.toArray(new String[bufSize]);
      Phrase newPhrase = new Phrase(wordArray);

      // Is this phrase is already mapped?
      Phrase alreadySeen = (Phrase) phraseMap.get(newPhrase);
      if (alreadySeen != null) {
        // yes, just increment the old phrase
        alreadySeen.incrementFrequency();
      } else {
        // no, insert the new phrase
        phraseMap.put(newPhrase, newPhrase);
      }
      // remove the first word so the next phrase will start with the next word
      wordBuffer.removeFirst();
    }
  }

  /**
   * The number of words in a phrase.
   */
  private final int phraseLength;

  /**
   * Maps all phrases that have been seen.
   */
  private final Map phraseMap = new HashMap();

  /**
   * Determines which words are stop words and non-stop-words.
   */
  private final WordAnalyzer wAnalyzer;

  /**
   * The current set of words, used to construct overlapping phrases.
   */
  private final LinkedList wordBuffer = new LinkedList();

}
